package org.helgoboss.janana_ref_impl;

import java.io.File;
import java.net.URI;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.BundleException;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
import org.apache.felix.framework.util.FelixConstants;
import java.io.File;
import java.util.Map;
import java.util.HashMap;
import java.util.Properties;
import java.io.IOException;
import java.io.FileInputStream;
import org.osgi.framework.Bundle;
import org.helgoboss.janana.Janana;
import org.osgi.framework.Constants;
import java.util.List;
import java.util.ArrayList;
import org.osgi.service.log.LogService;

public class JananaRefImpl implements Janana, BundleActivator {
	
    private URI systemBundleLocation;
	private Framework framework;
	private Runnable stopHandler;
    
    
	public void start(BundleContext context) {
        context.registerService(Janana.class.getName(), this, null);
    }
    
	public void stop(BundleContext context) {
    }
    
    public void setStopHandler(Runnable stopHandler) {
        this.stopHandler = stopHandler;
    }
    
    public void setSystemBundleLocation(URI systemBundleLocation) {
        this.systemBundleLocation = systemBundleLocation;
    }
    
    public void deleteBundleCache() {
        File cacheDir = getCacheDir();
        if (cacheDir.exists()) {
            if(!deleteRecursively(cacheDir)) {
                throw new BundleCacheDeletionFailedException();
            }
        }
    }
    
    private boolean deleteRecursively(File file) {
        if(file.isDirectory()) {
            for (File childFile: file.listFiles()) {
                if (!childFile.getName().equals(".") && !childFile.getName().equals("..")) {
                    boolean successfull = deleteRecursively(childFile);
                    if (!successfull) {
                        return false;
                    }
                }
            }
            return file.delete();
        } else {
            return file.delete();
        }
    }
    
    public void stop() {
        try {
            /* Returns immediately because the framework stop is done asynchronously in another thread */
            framework.stop();
            
            if (stopHandler != null) {
                /* Give Janana client the chance to do additional things while the framework is shutting down. For example, if the
                 * framework stop takes too much time, it could kill some threads or stop the complete JVM.
                 */
                Thread stopHandlerThread = new Thread(stopHandler);
                stopHandlerThread.setDaemon(true);
                stopHandlerThread.start();
            }
        } catch (BundleException ex) {
            throw new RuntimeException(ex);
        }
    }
	
    /**
     * This method blocks until the framework has been completely stopped.
     */
	public void start() {        
        /* Read user janana system property file residing in user home (for example, good for setting the HttpService port) */
        
        addToSystemProperties(getUserSystemPropertiesFile());
        
        
        /* Set further system properties */
        
        System.setProperty("org.helgoboss.janana.user_data_dir", getUserDataDir().getAbsolutePath());
        System.setProperty("org.helgoboss.janana.home_dir", getHomeDir().getAbsolutePath());
                    
        
        /* Create framework */
        
        try {
            FrameworkFactory factory = (FrameworkFactory) Class.forName("org.apache.felix.framework.FrameworkFactory").newInstance();
            Map<String, Object> frameworkConfig = new HashMap<String, Object>();
            frameworkConfig.put(Constants.FRAMEWORK_STORAGE, getCacheDir().getAbsolutePath());
            /* Export janana api, sun.misc and osgi compendium api */
            frameworkConfig.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, "org.helgoboss.janana; version=${version-in-osgi-format},sun.misc,org.osgi.util.gsm;version=1.0.1,org.osgi.service.deploymentadmin.spi;uses:=org.osgi.service.deploymentadmin;version=1.0.1,info.dmtree.spi;version=1.0,org.osgi.service.component;version=1.1,org.osgi.service.monitor;version=1.0,org.osgi.util.xml;version=1.0.1,org.osgi.service.wireadmin;version=1.0,info.dmtree.registry;uses:=info.dmtree.notification;version=1.0,info.dmtree.notification.spi;uses:=info.dmtree.notification;version=1.0,org.osgi.service.log;version=1.3,info.dmtree.security;version=1.0,org.osgi.service.prefs;version=1.1.1,org.osgi.service.metatype;version=1.1,org.osgi.service.io;version=1.0,org.osgi.service.application;version=1.1,org.osgi.service.http;version=1.2.1,org.osgi.util.measurement;version=1.0.1,org.osgi.util.mobile;version=1.0,org.osgi.service.provisioning;version=1.2,org.osgi.service.cm;version=1.3,org.osgi.service.deploymentadmin;version=1.1,org.osgi.service.event;version=1.2,org.osgi.service.upnp;version=1.1,org.osgi.service.device;version=1.1,org.osgi.util.tracker;version=1.4,info.dmtree.notification;version=1.0,org.osgi.util.position;uses:=org.osgi.util.measurement;version=1.0.1,org.osgi.service.useradmin;version=1.1");
            List<BundleActivator> activators = new ArrayList<BundleActivator>();
            activators.add(this);
            frameworkConfig.put(FelixConstants.SYSTEMBUNDLE_ACTIVATORS_PROP, activators);
            /* Let configuration admin save configuration outside of bundle cache so a --clean doesn't delete the configuration */
            frameworkConfig.put("felix.cm.dir", new File(getUserDataDir(), "var/configuration-admin").getAbsolutePath());
            framework = factory.newFramework(frameworkConfig);
            
            
            /* Start framework */
            
            framework.init();
            framework.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
                
        
    
        /* Install bootstrapper if not already installed */
        
        if (!bootstrapperIsInstalled()) {
            try {
                installBootstrapper();
            } catch (Exception ex) {
                throw new FirstStartFailedException(ex);
            }
        }
        try {
			framework.waitForStop(0);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
    
    public void log(int level, String message, Throwable throwable) {
        BundleContext bundleContext = framework.getBundleContext();
        ServiceReference ref = bundleContext.getServiceReference(LogService.class.getName());
        if (ref != null) {
            LogService logService = (LogService) bundleContext.getService(ref);
            if (logService != null) {
                logService.log(level, message, throwable);
            }
        }
    }
    
    public void addToSystemProperties(File propFile) {
        Properties props = new Properties();
        if (propFile.exists()) {
            try {
                props.load(new FileInputStream(propFile));
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
            System.getProperties().putAll(props);
        }
    }
    
    
	public void installSystemBundle(String symbolicId) {
        try {
            framework.getBundleContext().installBundle(getSystemBundleLocation(symbolicId + ".jar")).start();
        } catch (BundleException ex) {
            throw new RuntimeException(ex);
        }
	}
	
    public File getUserDataDir() {
        return new File(System.getProperty("user.home") + "/.janana");
    }
    
    public File getHomeDir() {
        return new File(System.getProperty("user.dir"));
    }
	
    private File getCacheDir() {
        return new File(getUserDataDir(), "var/bundle-cache");
    }
    
    private File getUserSystemPropertiesFile() {
        return new File(getUserDataDir(), "etc/system.properties");
    }
    
    /**
     * Returns the absolute location of the given OSGi bundle jar as an URL string.
     */
	private String getSystemBundleLocation(String jarFileName) {
		return systemBundleLocation.toString() + "/" + jarFileName;
	}
    
    private void installBootstrapper() {
        installSystemBundle("org.helgoboss.janana-bootstrapper");
    }
    
    /**
     * Returns whether the janana-bootstrapper is installed.
     */
    private boolean bootstrapperIsInstalled() {
        for (Bundle bundle: framework.getBundleContext().getBundles()) {
            if (bundle.getSymbolicName().equals("org.helgoboss.janana-bootstrapper")) {
                return true;
            }
        }
        return false;
    }
}